--[[
/*
 * Copyright (c) 2012 OptiCare Systems Ltd
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 *
 * Authors: Kevin Martin (kev82@khn.org.uk)
 */
]]

function mergeTables(...)
    local tbls = {...}
    local rv = {}
    for i, t in ipairs(tbls) do
        for k, v in pairs(t) do rv[k] = v end
    end
    return rv
end

local function forceauth()
	local f = require("gearman").caller("rackspace_cf_forceauth")
	f("")
end

local function authdetails()
	local sbrun = require("sandbox").run
	local f = require("gearman").caller("rackspace_cf_authdetails")
	local rv = sbrun(f(""))
	assert(rv.StorageURL)
	assert(rv.AuthToken)

	return rv.StorageURL, rv.AuthToken
end

local function blankConnection()
	local newcurl = require("curleasy").connector
	local rp = require("_rackspace-cf_ResponseParser")

	local function authConnect(self, connect)
		local response = connect()
		--If it's not a not auth (401) then return the result
		if not rp.responseNotAuth(response) then
			return response
		end

		forceauth()
		local newStorageURL, newAuthToken = authdetails()

		--We need to rewrite the old url with the new storage service
		local oldurl = connect:retrieve("url")
		connect:url(string.format("%s%s", newStorageURL, oldurl:sub(self.StorageURL:len()+1)))
		--And rewrite the auth token header
		local hdrs = connect:retrieve("headers")
		hdrs["X-Auth-Token"] = newAuthToken
		connect:headers(hdrs)

		response = connect()
		if rp.responseNotAuth(response) then
			error("Failed to reauthenticate")
		end

		self.StorageURL = newStorageURL
		self.AuthToken = newAuthToken

		return response
	end
		
	local function containerIter(self)
		local connect = newcurl()
		connect:headers({
		 ["X-Auth-Token"]=self.AuthToken})
		connect:url(self.StorageURL)
		connect:options({addheaders=1})

		local response = authConnect(self, connect)
		
		if not rp.responseOK(response) then
			error("Container Iter Failure")
		end
		
		local body = rp.getResponseBody(response)
		return body:gmatch("[^\r\n]+")
	end

	local function itemIter(self, container, prefix)
		local url = string.format("%s/%s", self.StorageURL, container)
		if type(prefix) == "string" then
			url = string.format("%s?prefix=%s", url, prefix)
		end
			
		local connect = newcurl()
		connect:headers({
		 ["X-Auth-Token"]=self.AuthToken})
		connect:url(url)
		connect:options({addheaders=1})

		local response = authConnect(self, connect)
		
		if rp.responseNoContent(response) then
			--either contain is empty or doesn't exist. Unfortunately
			--I can't tell the difference here, so return empty iterator
			--in both cases
			return function() return nil end
		end

		if not rp.responseOK(response) then
			error("Item Iter Failure")
		end

		local body = rp.getResponseBody(response)
		return body:gmatch("[^\r\n]+")
	end

	local function getObject(self, container, object)
		local connect = newcurl()
		connect:headers({
		 ["X-Auth-Token"]=self.AuthToken})
		connect:url(string.format("%s/%s/%s", self.StorageURL,
		 container, object))
		connect:options({addheaders=1})

		local response = authConnect(self, connect)
		
		if not rp.responseOK(response) then
			error("GetObject Failure")
		end
		
		local body = rp.getResponseBody(response)
		return body
	end

	local function putObject(self, container, object, data, metadata)
		--construct the metadata headers
		if metadata == nil then metadata = {} end
		local metahdrs = {}
		for k,v in pairs(metadata) do
			metahdrs[string.format("X-Object-Meta-%s", k)] = v
		end

		local connect = newcurl()
		connect:url(string.format("%s/%s/%s", self.StorageURL,
		 container, object))
		connect:headers(mergeTables({
		 ["X-Auth-Token"] = self.AuthToken,
		 ["Expect"] = "",
		 }, metahdrs))
		connect:options({addheaders=1})
		connect:put(data)

		local response = authConnect(self, connect)
		
		if not rp.responseOK(response) then
			error("PutObject Failure")
		end
		
		local body = rp.getResponseBody(response)
		return body
	end

	local function containerExists(self, container)
		local connect = newcurl()
		connect:headers({
		 ["X-Auth-Token"]=self.AuthToken})
		connect:url(string.format("%s/%s", self.StorageURL, container))
		connect:options({addheaders=1,no_body=1})

		local response = authConnect(self, connect)

		if rp.responseNotFound(response) then
			return false
		end

		if not rp.responseOK(response) then
			error("containerExists failure")
		end

		return true
	end

	local function objectExists(self, container, object)
		local connect = newcurl()
		connect:headers({
		 ["X-Auth-Token"]=self.AuthToken})
		connect:url(string.format("%s/%s/%s", self.StorageURL,
		 container, object))
		connect:options({addheaders=1,no_body=1})

		local response = authConnect(self, connect)

		if rp.responseNotFound(response) then
			return false
		end

		if not rp.responseOK(response) then
			error("objectExists failure")
		end

		return true
	end

	local function objectMeta(self, container, object)
		local connect = newcurl()
		connect:headers({
		 ["X-Auth-Token"]=self.AuthToken})
		connect:url(string.format("%s/%s/%s", self.StorageURL,
		 container, object))
		connect:options({addheaders=1,no_body=1})

		local response = authConnect(self, connect)

		if rp.responseNotFound(response) then
			error("Object does not exist")
		end

		if not rp.responseOK(response) then
			error("objectMeta failure")
		end

		local headers = rp.getResponseHeaders(response)
		local rv = {}
		for k,v in pairs(headers) do
			local meta = k:match("X%-Object%-Meta%-(%g[%g%-]*)$")
			if meta ~= nil then
				rv[meta] = v
			end
		end
		return rv
	end

	local tbl = {}
	tbl.containers = containerIter
	tbl.items = itemIter
	tbl.object = getObject
	tbl.updateobj = putObject
	tbl.isContainer = containerExists
	tbl.isObject = objectExists
	tbl.objmeta = objectMeta
	return tbl
end

local function NewConnection()
	local conn = blankConnection()
	conn.StorageURL, conn.AuthToken = authdetails()
	return conn
end

return {
 connection=NewConnection
}
